<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

interface Props {
  data: any[]
  width?: number
  height?: number
  radius?: number
  near?: number
  nodeType?: string
  customNodeCanvas?: Function
}

const props = withDefaults(defineProps<Props>(), {
  width: 375,
  height: 375,
  radius: 800,
  near: 2200,
  nodeType: 'text'
})

const emit = defineEmits<{
  (e: 'item-click', item: any): void
}>()

const container = ref()

const containerWidth = props.width
const containerHeight = props.height
const radius = props.radius
const near = props.near

const list = [...props.data]

const initThree = () => {
  // 创建场景
  const scene = new THREE.Scene()

  /**
   * 相机设置
   */
  const width = containerWidth //窗口宽度
  const height = containerHeight //窗口高度

  const camera = new THREE.PerspectiveCamera(50, containerWidth / containerHeight, 1, 10000)
  camera.position.set(0, 0, near)

  const group = new THREE.Group()

  // 将数组中的信息画到球上
  const length = list.length
  list.forEach(async (item: any, index) => {
    const phi = Math.acos(-1 + (2 * index) / length)
    const theta = Math.sqrt(length * Math.PI) * phi

    const sprite = await createSprite(item)
    if (props.nodeType === 'text') {
      sprite.scale.set(160, 80, 1)
    } else if (props.nodeType === 'avatar') {
      sprite.scale.set(80, 80, 1)
    } else {
      sprite.scale.set(100, 100, 1)
    }
    sprite.position.setFromSphericalCoords(radius, phi, theta)
    sprite.userData = {
      ...item
    }

    group.add(sprite)
  })

  scene.add(group)

  // 设置雾
  const far = near + 550
  const color = 'black'
  scene.fog = new THREE.Fog(color, near, far)
  scene.background = new THREE.Color(color)

  // 创建渲染器对象
  const renderer = new THREE.WebGLRenderer({
    antialias: true
  })
  renderer.setSize(width, height) //设置渲染区域尺寸
  renderer.shadowMap.enabled = false
  // renderer.setClearColor(0x000000, 1) //设置背景颜色
  renderer.setPixelRatio(window.devicePixelRatio)

  container.value.appendChild(renderer.domElement)

  //执行渲染操作   指定场景、相机作为参数
  renderer.render(scene, camera)

  // 光线投射
  const raycaster = new THREE.Raycaster()
  const pointer = new THREE.Vector2()

  function onPointerClick(event: any) {
    pointer.x = ((event.clientX - container.value.offsetLeft) / containerWidth) * 2 - 1
    pointer.y = -((event.clientY - container.value.offsetTop) / containerHeight) * 2 + 1

    // 通过摄像机和鼠标位置更新射线
    raycaster.setFromCamera(pointer, camera)

    // 计算物体和射线的焦点
    const intersects = raycaster.intersectObjects(group.children)

    const target = intersects![0]
    if (target) {
      // console.log(target.object.userData)
      emit('item-click', target.object.userData)
    }
  }

  const controls = new OrbitControls(camera, renderer.domElement)

  function render() {
    controls.update()
    renderer.render(scene, camera) //执行渲染操作
    group.rotateY(-0.001) //每次绕y轴旋转0.01弧度
    group.rotateX(0.001)

    requestAnimationFrame(render) //请求再次执行渲染函数render
  }
  render()

  container.value.addEventListener('click', onPointerClick)
}

async function createSprite(obj: any) {
  let nodeCanvas
  if (props.nodeType === 'text') {
    nodeCanvas = makeNodeCanvas4Text(obj, 160, 80)
  } else if (props.nodeType === 'avatar') {
    nodeCanvas = await makeNodeCanvas4Avatar(obj, 160)
  } else {
    nodeCanvas = props.customNodeCanvas ? props.customNodeCanvas(obj) : makeNodeCanvas4Text(obj, 160, 80)
  }

  const texture = new THREE.CanvasTexture(nodeCanvas)
  texture.generateMipmaps = false
  texture.minFilter = THREE.LinearFilter
  texture.magFilter = THREE.LinearFilter

  const pinMaterial = new THREE.SpriteMaterial({
    map: texture
  })
  const mesh = new THREE.Sprite(pinMaterial)

  return mesh
}

// 生成节点canvas - 文本
function makeNodeCanvas4Text(obj: any, width: any, height: any) {
  const text = obj.name
  const canvas = document.createElement('canvas')
  canvas.width = width
  canvas.height = height
  const ctx: any = canvas.getContext('2d')

  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)

  // 小圆点
  const circleX = width / 2
  const circleY = height / 2 + height / 4
  const radius = height / 8
  const gradient = ctx.createRadialGradient(circleX, circleY, 0, circleX, circleY, radius)
  gradient.addColorStop(0, 'rgba(255, 255, 255, 1)')
  gradient.addColorStop(1, 'rgba(255, 255, 255, 1)')
  ctx.beginPath()
  ctx.arc(circleX, circleY, radius, 0, 2 * Math.PI, true)
  ctx.fillStyle = gradient
  ctx.fill()
  ctx.closePath()

  // 文字
  ctx.beginPath()
  ctx.translate(width / 2, height / 3)
  ctx.fillStyle = '#ffffff' //文本填充颜色
  ctx.font = `${height / 2}px Arial` //字体样式设置
  ctx.textBaseline = 'middle' //文本与fillText定义的纵坐标
  ctx.textAlign = 'center' //文本居中(以fillText定义的横坐标)
  ctx.fillText(text, 0, 0)
  ctx.closePath()

  return ctx.canvas
}

// 生成节点canvas - 头像
function makeNodeCanvas4Avatar(obj: any, size: number) {
  return new Promise((resolve) => {
    const avatar = obj.avatar
    const canvas = document.createElement('canvas')
    canvas.width = size
    canvas.height = size
    const ctx: any = canvas.getContext('2d')

    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)

    const img = new Image(size, size)
    img.src = avatar

    img.onload = function () {
      // 绘制第一个圆
      ctx.beginPath()
      //圆心x、y的坐标，半径，起始角，结束角，顺时针画
      ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2)
      //将圆形剪切
      ctx.clip()
      //再画一个不存在的圆，避免画出来的微信头像有个黑圆圈
      ctx.beginPath()
      ctx.arc(0, 0, 0, 0, Math.PI * 2, false)
      ctx.drawImage(img, 0, 0, size, size)
      ctx.stroke()

      resolve(ctx.canvas)
    }
  })
}

onMounted(() => {
  initThree()
})
</script>

<template>
  <div ref="container" class="planet-container"></div>
</template>

<style lang="scss" scoped></style>
